#include <iostream>
#include <thread>
#include <mutex>
#include <string>
#include <deque>
#include <atomic>
#include <vector>
#include <map>
#include <queue>
#include <unordered_map>

#include "package.h"
#include "uniform_socket.h"
#include "uniform_time.h"
#include "types.h"

using namespace std;

struct client
{
	thread t;

	sockaddr_in addr;
	char ip[20];
	int port;
	mutex msend;
	uint stamp;
	queue<package> recvQueue;
	queue<package> sendQueue;
	uniform_socket_t s;
};

map<int, client> liveClientList;
mutex m;
atomic_bool retSignal;
string serverName;
void Recv(uniform_socket_t s, int id)
{
	for (;;)
	{
		package p;
		int len = recv(s, (char*)&p, bufSize, 0);
		liveClientList[id].recvQueue.push(p);
		//send(s, (char*)&p, bufSize, 0);
		if (len <= 0)
		{
			printf("Client %d disconnected\n", id);
			m.lock();
			liveClientList.erase(id);
			m.unlock();
			return;
		}
		if (!p.isLast())
		{
			continue;
		}
		printf("Recieved Type %d, func %c package from %d\n", p.type & 0xf0, (p.type & 0x0f) + 'a', p.from);
		if ((p.type & 0xf0) == TYPE_ASK)
		{
			switch ((p.type & 0x0f) + 'a')
			{
				case 'b':
				case 'g':
				{
					char goodbyeStr[30];
					sprintf(goodbyeStr, "Goodbye! Client %d.", id);
					package r(TYPE_RES | ('b' - 'a'), 0, strlen(goodbyeStr) + 1, p.stamp, 0, 1, goodbyeStr);
					liveClientList[id].msend.lock();
					send(s, (char*)&r, r.len, 0);
					liveClientList[id].msend.unlock();
					printf("Client %d closed connection\n", id);
					m.lock();
					liveClientList.erase(id);
					m.unlock();
					return;
				}
				case 'c':
				{
					string timeStr = GetCurrentTime();
					package r(TYPE_RES | ('c' - 'a'), 0, timeStr.length() + 1, p.stamp, 0, 1, timeStr.c_str());
					liveClientList[id].msend.lock();
					send(s, (char*)&r, r.len, 0);
					liveClientList[id].msend.unlock();
					break;
				}
				case 'd':
				{
					package r(TYPE_RES | ('d' - 'a'), 0, serverName.length() + 1, p.stamp, 0, 1, serverName.c_str());
					liveClientList[id].msend.lock();
					send(s, (char*)&r, r.len, 0);
					liveClientList[id].msend.unlock();
					break;
				}
				case 'e':
				{
					string str;
					char tmp[256];
					sprintf(tmp, "%lu ", liveClientList.size());
					str += tmp;
					for (auto it = liveClientList.begin(); it != liveClientList.end(); ++it)
					{
						int nid = it->first;
						sprintf(tmp, "%d %s %d ", it->first, liveClientList[nid].ip, liveClientList[nid].port);
						str += tmp;
					}

					liveClientList[id].msend.lock();
					split(liveClientList[id].sendQueue, TYPE_RES | ('e' - 'a'), 0, p.stamp, str.c_str(), str.length());
					while (!liveClientList[id].sendQueue.empty())
					{
						send(s, (char*)&liveClientList[id].sendQueue.front(), liveClientList[id].sendQueue.front().len, 0);
						liveClientList[id].sendQueue.pop();
					}
					liveClientList[id].msend.unlock();
					break;
				}
				case 'f':
				{
					char* fbuf = nullptr;
					uint fblen = 0;
					compose(liveClientList[id].recvQueue, fbuf, fblen);
					int did;
					sscanf(fbuf, "%d", &did);
					int offset;
					for (offset = 0; fbuf[offset] != ' '; offset++) {}
					offset++;
					if (liveClientList.count(did))
					{
						liveClientList[did].msend.lock();
						split(liveClientList[did].sendQueue, 
							TYPE_IND | ('f' - 'a'), id, p.stamp, fbuf + offset, fblen - offset);
						while (!liveClientList[did].sendQueue.empty())
						{
							send(liveClientList[did].s, 
								(char*)&liveClientList[did].sendQueue.front(), 
								liveClientList[did].sendQueue.front().len, 0);
							liveClientList[did].sendQueue.pop();
						}
						liveClientList[did].msend.unlock();

						while (liveClientList[id].recvQueue.empty()) {}
						package r = liveClientList[id].recvQueue.front();
						liveClientList[id].recvQueue.pop();
						liveClientList[id].msend.lock();
						send(s, (char*)&r, r.len, 0);
						liveClientList[id].msend.unlock();
					}
					else
					{
						char errStr[60];
						sprintf(errStr, "Error! Client %d not exists or not connected", did);
						package r(TYPE_RES | 'f' - 'a', 0, strlen(errStr) + 1, p.stamp, 0, 1, errStr);
						liveClientList[id].msend.lock();
						send(s, (char*)&r, r.len, 0);
						liveClientList[id].msend.unlock();
					}
					break;
				}
				default:
				{
					break;
				}
			}
		}
		else if ((p.type & 0xf0) == TYPE_RES)
		{
			liveClientList[p.from].recvQueue.push(p);
		}

		while (!liveClientList[id].recvQueue.empty())
		{
			liveClientList[id].recvQueue.pop();
		}
	}
}

void Accept(uniform_socket_t s)
{
	int id = 0;
	for (;;)
	{
		sockaddr_in clntAddr;
		u32 nSize = sizeof(clntAddr);
		uniform_socket_t clientSocket;
		clientSocket = accept(s, (uniform_sockaddr*)&clntAddr, &nSize);
		client tmp;
		id++;
		{
			lock_guard<mutex> lck(m);
			liveClientList[id].t = thread(Recv, clientSocket, id);
			liveClientList[id].addr = clntAddr;
			liveClientList[id].s = clientSocket;
			liveClientList[id].stamp = 0;
			strcpy(liveClientList[id].ip, inet_ntoa(clntAddr.sin_addr));
			liveClientList[id].port = clntAddr.sin_port;
            liveClientList[id].t.detach();
		}
		char helloMsg[30];
		sprintf(helloMsg, "Hello! Your id is:%d.", id);
		package helloPackage(TYPE_RES, 0, strlen(helloMsg) + 1, 0, 0, 1, helloMsg);
		send(clientSocket, (char*)&helloPackage, helloPackage.len, 0);
		printf("Client %d connected\n", id);
	}
}

void Input()
{
	string s;
	cin >> s;
	while (s != "exit")
	{
		cin >> s;
	}
	retSignal = true;
}

int main()
{
	serverName = "My server";
	printf("Server name = My server\n");
	uniform_socket_init();

	uniform_socket_t serverSocket = socket(AF_INET, SOCK_STREAM, 0);
	sockaddr_in addr{};
	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = INADDR_ANY;
	addr.sin_port = htons(5050);

	int ret = ::bind(serverSocket, (const sockaddr*)&addr, sizeof(addr));
	listen(serverSocket, 20);
	thread t_input(Input);
	thread t_accept(Accept, serverSocket);
	puts("Server ready");
	for (;;)
	{
		std::this_thread::sleep_for(std::chrono::microseconds(10));
		if (retSignal) {
			uniform_socket_cleanup();
			exit(0);
		}
	}
}
